import { Line2 } from "three/examples/jsm/lines/Line2";
import { App, Point } from "../../Engine";
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
import { AlwaysStencilFunc, Box3, Box3Helper, BoxGeometry, Color, KeepStencilOp, Mesh, MeshBasicMaterial, NotEqualStencilFunc, Object3D, Path, Ray, Raycaster, ReplaceStencilOp, Vector3 } from "three";
import Const from "../../application/constants/Const";
import Helper from "../../utils/Helper";
import MeshUtil from "../../renderExtension/utils/MeshUtil";
/**
 * 线组件
 */
export default class Polyline extends Line2 {
    private pointArray: Array<Point>;
    private positionArray: Array<number>;
    private colorArray: Array<number>;
    private lineColor: Color;
    private blockedColor: Color;
    private linePoistion;// = new Vector3(0, 0, 0);
    private path: Path;
    public param: { pointArray?: Array<Point>, color?: string, blockedColor?:string, lineWidth?: number, depthTest?: boolean, transparent?: boolean, opacity?:number, stencilRef?:number};

    /**
     * 线条组件
     * @param param 线条组件用到的参数
     * @param param.pointArray 组成线条的点数组
     * @param param.color 线条的颜色
     * @param param.lineWidth 线条的宽度, 可选参数，默认为1
     * @param param.depthTest 是否开启深度检测，可选参数，默认为false
     * @param param.transparent 是否开启透明，可选参数，默认为true
     * @example let pointArray = [ //3857表示web墨卡托坐标系，4326表示经纬度坐标
            new GE.Point(13376713.90129997, 3540579.2959903134, 0, '3857'),
            new GE.Point(13376711.98134067, 3539826.5375406537, 0, '3857'),
            new GE.Point(13377952.49603797, 3539759.3270052206, 0, '3857'),
            new GE.Point(13378008.185227688, 3540675.3111583497, 0, '3857'),
            new GE.Point(13377184.375437068, 3540339.2581293588, 0, '3857'),
        ];
        let lineData = {
            pointArray: pointArray,  //多边形的顶点数组
            color: '#00FA9A',
            lineWidth: 5,  //线的宽度
        };
        polyline = new GE.Polyline(lineData);
        app.add(polyline);
     */
    public constructor(param: { pointArray?: Array<Point>, color?: string, blockedColor?:string, lineWidth?: number, depthTest?: boolean, transparent?: boolean, opacity?:number, stencilRef?:number}) {
        super();

        this.param = param;
        this.path = new Path();

        this.pointArray = (!param.pointArray) ? new Array<Point>() : param.pointArray;
        this.positionArray = new Array<number>();
        this.colorArray = new Array<number>();

        this.lineColor = new Color(param.color || '0x000000');
        this.blockedColor = new Color(param.blockedColor || '0xFFFFFF');
        this.geometry = new LineGeometry();
        this.parseGeometryData(param.pointArray);

        if (this.linePoistion) {
            this.position.copy(this.linePoistion);

            this.geometry.setPositions(this.positionArray);
            this.geometry.setColors(this.colorArray);
        }

        this.material = new LineMaterial({
            // color: this.lineColor,
            linewidth: param.lineWidth || 1, // in world units with size attenuation, pixels otherwise
            vertexColors: true,
            dashed: false,
            alphaToCoverage: false,
            transparent: (param.transparent != undefined ? param.transparent : true), // param.transparent || true, // 
            opacity: param.opacity || 1,
            depthTest: param.depthTest || false,
        });
        if(this.param.stencilRef != undefined){
            //设置模版缓冲区，用this.param.stencilRef覆盖模版缓冲区
            this.material.stencilWrite = true;
            this.material.stencilRef = this.param.stencilRef;
            this.material.stencilFuncMask = 0xff;
            this.material.stencilFunc = NotEqualStencilFunc; //AlwaysStencilFunc;
            this.material.stencilFail = ReplaceStencilOp;
            this.material.stencilZFail = ReplaceStencilOp;
            this.material.stencilZPass = ReplaceStencilOp;
            this.material.needsUpdate = true;
        }
        this.material.fog = true;
        this.material['flatShading'] = true;
        this.material.resolution.set(window.innerWidth, window.innerHeight); // resolution of the viewport
        this.scale.set(1, 1, 1);
    }

    /**开启模版过滤 */
    public filterStencil(filterValue){
        this.material.stencilWrite = true;
        this.material.stencilRef = filterValue;
        this.material.stencilFuncMask = 0xff;
        this.material.stencilFunc = NotEqualStencilFunc;
        this.material.stencilFail = KeepStencilOp;
        this.material.stencilZFail = KeepStencilOp;
        this.material.stencilZPass = KeepStencilOp;
        this.material.needsUpdate = true;
    }

    private parseGeometryData(pointArray: Array<Point>) {
        if (!pointArray || pointArray.length == 0) {
            return;
        }

        this.positionArray.length = 0;
        this.colorArray.length = 0;

        for (let i = 0; i < pointArray.length; i++) {
            let point = pointArray[i];
            point.toEPSGWeb();

            if (i == 0) {
                this.linePoistion = point.clone();
            }
            point = point.clone().sub(this.linePoistion);
            this.positionArray.push(point.x, point.y, point.z);
            this.colorArray.push(this.lineColor.r, this.lineColor.g, this.lineColor.b);
        }
    }

    /**
     * 修改线的颜色
     * @param color 线条的颜色
     * @example  polyline.changeColor('#FFB6C1');
     */
    public changeColor(color: string) {
        this.lineColor = new Color(color);
        this.material.color = this.lineColor;

        let length = this.colorArray.length / 3;
        this.colorArray = [];

        for (let i = 0; i < length; i++) {
            this.colorArray.push(this.lineColor.r, this.lineColor.g, this.lineColor.b);
        }
        this.geometry.setColors(this.colorArray);
    }

    /**
     * 修改线条的宽度
     * @param width 线条的宽度
     * @example polyline.changeWidth(5);
     */
    public changeWidth(width: number) {
        this.material.linewidth = width;
        this.material.needsUpdate = true;
    }

    /**
     * 在线中添加新的点，新添加的点在线的末尾
     * @param point 新的点
     * @example let endPoint = new GE.Point(13376713.90129997, 3540579.2959903134, 0, '3857');
        polyline.addPoint(endPoint);
     */
    public addPoint(point: Point) {
        point.toEPSGWeb();

        this.pointArray.push(point);

        if (!this.linePoistion) {
            this.linePoistion = point.clone();
            this.position.copy(this.linePoistion);
        }

        point = point.clone().sub(this.linePoistion);
        this.positionArray.push(point.x, point.y, point.z);
        this.colorArray.push(this.lineColor.r, this.lineColor.g, this.lineColor.b);

        this.updateGeometry(false);
    }

    /**
     * 修改线中的一个点
     * @param index 要修改点的索引。这个索引对应数组中的下标，索引值为0对应数组中的第一个元素 
     * @param newPoint 在索引处，用这个新的点代替之前的点
     * @example let updateIndex = 0; 
        let updatePoint = polyline.getPointByIndex(updateIndex);
        updatePoint.x += Math.random() * 100;
        updatePoint.y += Math.random() * 100;
        polyline.updatePoint(updateIndex, updatePoint);
     */
    public updatePoint(index: number, newPoint: Point) {
        if (index < 0 || index >= this.pointArray.length) {
            return;
        }

        newPoint.toEPSGWeb();

        this.pointArray[index] = newPoint.clone();
        if (index == 0) { //数组中0的位置是Polyline的基准点，如果基准点发生改变，Polyline中的每个点的位置都要重新计算
            this.parseGeometryData(this.pointArray); //更新Polyline的数组
            this.position.copy(this.linePoistion); //修改Polyline的基准点位置
        }

        newPoint = newPoint.clone().sub(this.linePoistion);

        let positionIndex = index * 3;
        this.positionArray[positionIndex] = newPoint.x;
        this.positionArray[positionIndex + 1] = newPoint.y;
        this.positionArray[positionIndex + 2] = newPoint.z;

        this.geometry.setPositions(this.positionArray);
        this.geometry.setColors(this.colorArray);

        let positionAttribute = this.geometry.getAttribute("position");
        positionAttribute.needsUpdate = true;
    }

    /**
     * 删除线中的一个点
     * @param index 要删除点的索引。这个索引对应数组中的下标，索引值为0对应数组中的第一个元素 
     * @example polyline.removePoint(0);
     */
    public removePoint(index: number) {
        if (index < 0 || index >= this.pointArray.length) {
            return;
        }

        this.pointArray.splice(index, 1);

        let positionIndex = index * 3;

        this.positionArray.splice(positionIndex, 3);
        this.colorArray.splice(positionIndex, 3);

        this.geometry.dispose();

        if (this.positionArray.length == 0) {
            return;
        }

        if (index == 0 && this.pointArray.length > 0) {
            this.linePoistion = this.pointArray[0].toEPSGWeb();
            this.updateGeometry(true);
            this.position.copy(this.linePoistion);
        } else {
            this.updateGeometry(false);
        }

    }

    /**
     * 获取线中的某个点
     * @param index 线中某个点的位置（从0开始）
     * @returns 某个位置上的点
     * @example  let poing = polyline.getPointByIndex(0);
     */
    public getPointByIndex(index: number) {
        if (index < 0 || index >= this.pointArray.length) {
            return;
        }
        return this.pointArray[index];
    }

    /**
     * 获取一个数组，该数组中包含线条的所有顶点
     * @returns 一个包含所有顶点的数组
     * @example let pointArray = polyline.getPointArray();
     */
    public getPointArray() {
        return this.pointArray;
    }

    /**
     * 往线条中适当的位置添加一个点
     * @param newPoint 
     */
    public insertPoint(newPoint: Point): { insertedPoint: Point, fitIndex: number } {
        newPoint.toEPSGWeb();
        let ray: Ray;
        let minDistance = 10;
        let fitIndex = -1;
        let fitPosition = new Vector3();

        for (let i = 0; i < this.pointArray.length - 1; i++) {
            let currentPoint = this.pointArray[i];
            let nextPoint = this.pointArray[i + 1];

            let direction = nextPoint.clone().sub(currentPoint).normalize();
            ray = new Ray(currentPoint, direction);
            let distance = ray.distanceToPoint(newPoint.clone());
            distance = Math.round(distance * 100) / 100; //保留distance两位小数点
            if (distance <= minDistance) {
                minDistance = distance;
                fitPosition = ray.closestPointToPoint(newPoint.clone(), fitPosition); //这里有优化空间，最好是在找出最适合的fitIndex后再求fitPosition
                if (this.isInSegementRange(fitPosition, currentPoint, nextPoint)) {
                    fitIndex = i;
                }
            }
        }

        if (fitIndex != -1) {
            let insertedPoint = new Point(fitPosition.x, fitPosition.y, fitPosition.z, Const.EPSGType.EPSGWeb);
            this.pointArray.splice(fitIndex + 1, 0, insertedPoint);

            this.updateGeometry(true);

            return { insertedPoint: insertedPoint, fitIndex: fitIndex };
        }

        return null;
    }

    private isInSegementRange(newPoint, currentPoint, nextPoint): boolean {
        if (newPoint.x < Math.min(currentPoint.x, nextPoint.x)) {
            return false;
        } else if (newPoint.x > Math.max(currentPoint.x, nextPoint.x)) {
            return false;
        }

        if (newPoint.y < Math.min(currentPoint.y, nextPoint.y)) {
            return false;
        } else if (newPoint.y > Math.max(currentPoint.y, nextPoint.y)) {
            return false;
        }

        if (newPoint.z < Math.min(currentPoint.z, nextPoint.z)) {
            return false;
        } else if (newPoint.z > Math.max(currentPoint.z, nextPoint.z)) {
            return false;
        }
        return true;
    }

    public replacePointArray(newPointArray) {
        this.pointArray.length = 0;
        this.pointArray = newPointArray.slice()

        this.updateGeometry(true);
    }

    private updateGeometry(isUpdateData: boolean) {
        this.geometry.dispose();
        this.geometry = new LineGeometry();
        if (isUpdateData) {
            this.parseGeometryData(this.pointArray);
        }
        this.geometry.setPositions(this.positionArray);
        this.geometry.setColors(this.colorArray);
    }

    public getPath() {
        for (let i = 0; i < this.positionArray.length; i += 3) {
            if (i == 0) {
                this.path.moveTo(this.positionArray[i], this.positionArray[i + 1]);
            } else {
                this.path.lineTo(this.positionArray[i], this.positionArray[i + 1]);
                this.path.moveTo(this.positionArray[i], this.positionArray[i + 1]);
            }
        }
        return this.path;
    }

    /**
     * 检测线条是否和objArray中的mesh有相交/擦边，如果有，就从第一个相交点/擦边点开始变化线条的颜色
     * @param objArray 
     * @returns {intersectState: number //0表示没有相交/擦边,1表示有相交点,2表示有擦边点,
     *  previouseIndex: number, //相交点的前一个顶点的数组下标
     *  nextIndex: number  //相交点的后一个顶点的数组下标
     * }  
     */
    public intersectObjects(objArray: Array<Object3D>, sidewipeDistance: number = 10) {
        if (!this.pointArray || this.pointArray.length <= 1) {
            return {intersectState: 0, previouseIndex: -1, nextIndex: -1};
        }

        let tempPositionArray = [];
        let temColorArray = [];

        let isIntersect = false;
        let isCrossWall = true; //是否穿越障碍物
        let isSidewipe = false; //是否擦边
        let previouseIndex= -1, nextIndex = -1; //相交点的前一个点和后一个点的数组下标
        for (let i = 0; i < this.pointArray.length - 1; i++) {
            let curPoint = this.pointArray[i];
            curPoint.toEPSGWeb();

            let nextPoint = this.pointArray[i + 1];
            nextPoint.toEPSGWeb();

            if (isIntersect) {
                tempPositionArray.push(
                    curPoint.x - this.linePoistion.x, curPoint.y - this.linePoistion.y, curPoint.z - this.linePoistion.z
                );
                temColorArray.push(
                    this.blockedColor.r, this.blockedColor.g, this.blockedColor.b
                );
                continue;
            }

            let directorVector = nextPoint.clone().sub(curPoint);
            let distance = directorVector.length();
            directorVector = directorVector.normalize();
            let ray: Raycaster = new Raycaster(curPoint, directorVector, 0, distance);
            ray.camera = App.getInstance().three.camera;

            let intersectObjects = objArray || [App.getInstance().three.scene];
            let intersectPoints = ray.intersectObjects(intersectObjects, true);
            let intersectionPosition;
            if (!intersectPoints || intersectPoints.length == 0) {
                intersectionPosition = this.intersectSidewipe(curPoint, nextPoint, intersectObjects, sidewipeDistance); 
                if(intersectionPosition){ //有擦边点
                    isCrossWall = false;
                    isSidewipe = true;
                }else{
                    isCrossWall = false;
                    isSidewipe = false;
                }
            }else{
                intersectionPosition = this.intersectSidewipe(curPoint, nextPoint, intersectObjects, sidewipeDistance); //得到擦边点
                if(intersectionPosition){
                    let distanceToSidewipe = curPoint.distanceTo(intersectionPosition); //从起点到擦边点的距离
                    let distanceToIntersect = curPoint.distanceTo(intersectPoints[0].point); //从起点到相交点的距离
                    if(distanceToSidewipe > distanceToIntersect){ //如果从从起点到相交点的距离小于从起点到擦边点的距离，取相交点
                        intersectionPosition = intersectPoints[0].point;
                        isCrossWall = true;
                        isSidewipe = false;
                    }else{
                        isCrossWall = false;
                        isSidewipe = true;
                    }
                }else{ //没有擦边点，取相交点
                    intersectionPosition = intersectPoints[0].point;
                    isCrossWall = true;
                    isSidewipe = false;
                }
            }

            // if (intersectPoints && intersectPoints.length > 0) {
            if (intersectionPosition) {
                previouseIndex = i;
                nextIndex = i+1;
                isIntersect = true;

                // let firstIntersect = intersectionPosition;
                let intersectPoint = new Vector3(intersectionPosition.x, intersectionPosition.y, intersectionPosition.z);

                tempPositionArray.push(
                    curPoint.x - this.linePoistion.x, curPoint.y - this.linePoistion.y, curPoint.z - this.linePoistion.z,
                    intersectPoint.x - this.linePoistion.x, intersectPoint.y - this.linePoistion.y, intersectPoint.z - this.linePoistion.z,
                    intersectPoint.x - this.linePoistion.x, intersectPoint.y - this.linePoistion.y, intersectPoint.z - this.linePoistion.z
                );

                temColorArray.push(
                    this.lineColor.r, this.lineColor.g, this.lineColor.b,
                    this.lineColor.r, this.lineColor.g, this.lineColor.b,
                    this.blockedColor.r, this.blockedColor.g, this.blockedColor.b
                );
            } else {
                tempPositionArray.push(
                    curPoint.x - this.linePoistion.x, curPoint.y - this.linePoistion.y, curPoint.z - this.linePoistion.z
                );
                temColorArray.push(
                    this.lineColor.r, this.lineColor.g, this.lineColor.b
                );
            }
        }

        let lastPoint = this.pointArray[this.pointArray.length - 1];
        tempPositionArray.push(
            lastPoint.x - this.linePoistion.x, lastPoint.y - this.linePoistion.y, lastPoint.z - this.linePoistion.z,
        );
        if (isIntersect) {
            temColorArray.push(
                this.blockedColor.r, this.blockedColor.g, this.blockedColor.b
            );
        } else {
            temColorArray.push(
                this.lineColor.r, this.lineColor.g, this.lineColor.b
            );
        }

        this.geometry.dispose();
        this.geometry = new LineGeometry();
        this.geometry.setPositions(tempPositionArray);
        this.geometry.setColors(temColorArray);
        this.computeLineDistances();
        
        let result: {intersectState: number, previouseIndex: number, nextIndex: number} = {intersectState: 0, previouseIndex: previouseIndex, nextIndex: nextIndex};
        if(isCrossWall){
            result.intersectState = 1;
        }else if(isSidewipe){
            result.intersectState = 2;
        }

        return result;
    }

    private boxHelperArray = [];
    private fitPosition;
    private isSuccess = false;
    //检测线段是否和物体距离太近
    private intersectSidewipe(currentPoint, nextPoint, objArray: Array<Object3D>, sidewipeDistance: number) {
        let app = App.getInstance();
        let middlePoint = new Point((currentPoint.x + nextPoint.x) / 2, (currentPoint.y + nextPoint.y) / 2, (currentPoint.z + nextPoint.z) / 2, currentPoint.EPSGType);
        let distance = currentPoint.distanceTo(nextPoint) + sidewipeDistance;

        let direction = nextPoint.clone().sub(currentPoint).normalize();
        let ray: Ray = new Ray(currentPoint, direction);

        const geometry = new BoxGeometry(sidewipeDistance, sidewipeDistance, distance);
        const material = new MeshBasicMaterial({ color: 0x00ff00 });
        const cube = new Mesh(geometry, material);
        cube.position.copy(middlePoint);
        cube.lookAt(nextPoint);
        app.add(cube);

        this.boxHelperArray.forEach(element => {
            MeshUtil.dispose(element);
        });

        this.fitPosition = null;
        this.isSuccess = false;
        let self = this;
        let cubeBox = new Box3().setFromObject(cube);
        let intersectCallback = function (object3D) {
            if (self.isSuccess) {
                return;
            }

            let meshBox = object3D[Const.MapConst.meshBox];
            if (meshBox) {
                let intersectionBox = cubeBox.clone().intersect(meshBox);
                if (intersectionBox.isEmpty()) {
                    return false;
                } else {
                    if (intersectionBox && !intersectionBox.isEmpty()) {

                        let tempIntersetPosition = new Vector3(-1, -1, -1);
                        let intersectPosition = ray.intersectBox(intersectionBox, new Vector3(-1, -1, -1));

                        if (intersectPosition && !intersectPosition.equals(tempIntersetPosition)) {
                            return self.checkMeshIntersect(object3D, intersectionBox, ray, sidewipeDistance, currentPoint, nextPoint, self.boxHelperArray, meshBox, cubeBox);
                        }
                    }

                    return true;
                }
            }
            return true;
        };

        for (let i = 0; i < objArray.length; i++) {
            let ground = objArray[i];

            if (ground['isMesh']) {
                intersectCallback(ground);
            }
            Helper.traverseObject(ground, intersectCallback);

            if (this.fitPosition) {
                this.fitPosition = ray.closestPointToPoint(this.fitPosition, new Vector3());
                break;
            }
        }

        MeshUtil.dispose(cube);
        return this.fitPosition;
    }

    private checkMeshIntersect(object3D, intersectionBox, ray, safeDistance, currentPoint, nextPoint, boxHelperArray, meshBox, cubeBox) {
        if (object3D['isMesh'] && (!object3D.children || (object3D.children && object3D.children.length == 0))) {
            // let app = App.getInstance();
            // const boxHelper = new Box3Helper(intersectionBox, 0xffff00);
            // app.add(boxHelper);
            // this.boxHelperArray.push(boxHelper);

            // const boxHelper2 = new Box3Helper(meshBox, 0x0000FF);
            // app.add(boxHelper2);
            // this.boxHelperArray.push(boxHelper2);

            // const cubeBoxHelper = new Box3Helper(cubeBox, 0xCD5C5C);
            // app.add(cubeBoxHelper);
            // this.boxHelperArray.push(cubeBoxHelper);

            // 获取几何体的顶点属性
            const positionAttr = (object3D as Mesh).geometry.getAttribute('position');
            const vertices = positionAttr.array; // 顶点数据数组
            const vertexCount = positionAttr.count; // 顶点数量

            // 更新模型的世界变换矩阵（重要！）
            object3D.updateMatrixWorld(true);

            let minDistance = 10;
            // 遍历所有顶点
            for (let i = 0; i < vertexCount; i++) {
                // 从数组读取局部坐标 (x, y, z)
                const x = vertices[i * 3];
                const y = vertices[i * 3 + 1];
                const z = vertices[i * 3 + 2];

                // 创建向量并应用世界矩阵
                const vertex = new Vector3(x, y, z);
                vertex.applyMatrix4(object3D.matrixWorld); // 关键转换步骤

                if (intersectionBox.containsPoint(vertex)) {
                    let curDistance = ray.distanceToPoint(vertex);
                    if (curDistance <= safeDistance / 2 && curDistance < minDistance) {
                        this.isSuccess = true;

                        let isOverLine = this.isGoOverLineSegment(currentPoint, nextPoint, vertex);
                         if (isOverLine) {
                            minDistance = curDistance;
                            this.fitPosition = nextPoint;
                            break;
                        }
                        minDistance = curDistance;
                        this.fitPosition = vertex;
                    }
                }
            }

            if (this.fitPosition) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断点otherPoint是否在lineStartPoint和lineEndPoint构成的线段上
     * @param {Vector3} lineStartPoint - 线段起点
     * @param {Vector3} lineEndPoint - 线段终点
     * @param {Vector3} otherPoint - 待检测点
     * @returns {boolean}
     */
    private isGoOverLineSegment(lineStartPoint, lineEndPoint, otherPoint) {
        let otherVector: Vector3 = otherPoint.clone().sub(lineStartPoint);
        let lineVector: Vector3 = lineEndPoint.clone().sub(lineStartPoint);
        let lineFragmentLength = lineVector.length();
        let projectionLength = (otherVector.dot(lineVector.normalize()));//  / (lineFragmentLength * lineFragmentLength);
        if (projectionLength > lineFragmentLength) {
            return true
        }
        return false;
    }
}